//
// Created by LiuYou on 2021/8/20.
//

#include <cstdlib>
#include <iostream>
#include <cstring>
#include <csignal>
#include <arpa/inet.h>
#include <unistd.h>
#include "ThreadPool2.hpp"
#include "HttpConnect2.hpp"
#include "DbConnectionPool.hpp"

/* 最大文件描述符*/
static const int MAX_FD = 65536;


void addSignal(int sig, __sighandler_t pFunction, bool restart = true) {
    struct sigaction sigAction{};
    sigAction.sa_handler = pFunction;
    if (restart) {
        sigAction.sa_flags |= SA_RESTART;
    }
    sigfillset( &sigAction.sa_mask );
    sigaction( sig, &sigAction, nullptr );
    // 判断。
}

void showError(int connectFd, const char* info) {
    printf( "%s", info );
    send( connectFd, info, strlen( info ), 0 );
    close( connectFd );
}

int main(int argc, char** argv) {
    if (argc <= 2) {
        std::cerr << "用法: " << basename( argv[0] ) << " ip port" << std::endl;
        exit( 1 );
    }
    char* ip = argv[1];
    int port = atoi( argv[2] );

    addSignal( SIGPIPE, SIG_IGN);


    // 创建数据库连接池
    DbConnectionPool* connPool = DbConnectionPool::getInstance();
    connPool->init( "localhost", "root", "123456", "yourdb", 3306, 8 );
//    connPool->init( "192.168.52.129", "root", "123456", "yourdb", 3306, 8 );
//    connPool->init( "127.0.0.1", "root", "123456", "yourdb", 3306, 8 );


    ThreadPool<HttpConnect>* pool = nullptr;
    try {
        pool = new ThreadPool<HttpConnect>( connPool );
    } catch (...) {
        exit( 1 );
    }


    /*****************************************************************************************/

    // 服务器接收http请求
    // 浏览器端发出http连接请求，主线程创建http对象接收请求并将所有数据读入对应buffer，将该对象插入任务队列，
    // 工作线程从任务队列中取出一个任务进行处理。

    /* 预先为每个可能的客户连接分配一个 HttpConnect对象。*/
    // 创建MAX_FD个http类对象
    // TODO 笔记: 这里浪费了内存。
    HttpConnect* users = new HttpConnect[MAX_FD];
    int userCount{0};
    // 初始化数据库读取表
    users->initMysqlResult( connPool );

    int listenFd = socket( PF_INET, SOCK_STREAM, 0 );
    linger tmp = {1, 0};
    setsockopt( listenFd, SOL_SOCKET, SO_LINGER, &tmp, sizeof( tmp ));

    sockaddr_in localAddress{};
    localAddress.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &localAddress.sin_addr );
    localAddress.sin_port = htons( port );

    int optVal{1};
    setsockopt( listenFd, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof( optVal ));

    int ret = bind( listenFd, (sockaddr*) &localAddress, sizeof( localAddress ));

    ret = listen( listenFd, 5 );

    // 创建内核事件表
    epoll_event epollEvents[10000];
    int epollFd = epoll_create( 5 );

    // 将listenfd放在epoll上, 为listenFd注册epoll监听事件。
    addFdToEpoll( epollFd, listenFd, false );
    // 将上述epollfd赋值给http类对象的m_epollfd成员变量。
    HttpConnect::epollFd = epollFd;

    while (true) {
        // 等待所监控文件描述符上有事件的产生
        int readyFdNum = epoll_wait( epollFd, epollEvents, 10000, -1 );
        if ((readyFdNum < 0) && (errno != EINTR)) {
            std::cerr << "epoll failure\n";
            break;

        }

        // 对所有就绪事件进行处理, 事件循环处理。 EventLoop
        for (int i = 0; i < readyFdNum; ++i) {
            int fd = epollEvents[i].data.fd;
            // 处理新到的客户连接
            if (fd == listenFd) {
// LT水平触发
                sockaddr_in remoteAddress{};
                socklen_t socklen = sizeof( remoteAddress );
                int connectFd = accept( listenFd, (sockaddr*) &remoteAddress, &socklen );
                if (connectFd < 0) {
                    fprintf( stderr, "accept(): %s", strerror(errno));
                    continue;
                }
                if (HttpConnect::userCount >= MAX_FD) {
                    showError( connectFd, "Internal server busy" );
                    continue;
                }
                /* 初始化客户连接。初始化这个词用的不好, 而是应该是配置。配置客户(浏览器端)连接。*/
                users[connectFd].init( connectFd, remoteAddress );
            }
                // 处理异常事件
            else if (epollEvents[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                /* 如果有异常, 直接关闭客户连接。*/
                users[fd].closeConnection();
            }
                // 处理客户连接上接收到的数据
            else if (epollEvents[i].events & EPOLLIN) {
                /* 根据读的结果, 决定是将任务添加到线程池, 还是关闭连接。*/
                // 读取对应的缓冲区
                // 将users数组的fd索引处的http连接对象来读取客户端发来的内容, 并将这个http对象放入到线程池中。
                // 让下游8个线程来争抢处理。
                // 这里是 服务器端浪费空间来提高运行效率了。因为一个进程程序可以打开的文件描述符最多不可能超过 MAX_FD 个, 65536个, 2的16次方。
                if (users[fd].read()) {
                    // 若监测到读事件，将该事件放入请求队列
                    // 将这个http连接对象放到线程池的请求队列中。
                    pool->append( users + fd );
                } else {
                    // 服务器关闭连接
                    users[fd].closeConnection();
                }
            } else if (epollEvents[i].events & EPOLLOUT) {
                /* 根据写的结果, 决定是否关闭连接。*/
                if (!users[fd].write()) {
                    users[fd].closeConnection();
                }
            } else {

            }
        }
    }

    close( epollFd );
    close( listenFd );
    delete[] users;
    delete pool;


    exit( 0 );
}
